package com.hyt.it.ogt.kq.service.bm.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hyt.core.util.FastJsonUtil;
import com.hyt.core.util.UUIDUtils;
import com.hyt.it.ogt.cj.enums.ComponentNameEnum;
import com.hyt.it.ogt.cj.service.IProjectCollectInfoService;
import com.hyt.it.ogt.kq.common.config.ConfigManager;
import com.hyt.it.ogt.kq.common.exception.GovInvalidRequestException;
import com.hyt.it.ogt.kq.common.gov.constant.ResponseCode;
import com.hyt.it.ogt.kq.common.gov.constant.fjcitybm.SendMessageTypeEnum;
import com.hyt.it.ogt.kq.common.govData.enums.kw.MessageSendStatusCode;
import com.hyt.it.ogt.kq.common.utils.GovRedisLock;
import com.hyt.it.ogt.kq.service.bm.feign.tps.TpsClient;
import com.hyt.it.ogt.kq.service.bm.mapper.ProjectMapper;
import com.hyt.it.ogt.kq.service.bm.mapper.StudentProjectMapper;
import com.hyt.it.ogt.kq.service.bm.model.dto.RegisterMessageDTO;
import com.hyt.it.ogt.kq.service.bm.model.entity.*;
import com.hyt.it.ogt.kq.service.bm.model.vo.StudentInfoParam;
import com.hyt.it.ogt.kq.service.bm.model.vo.StudentListVo;
import com.hyt.it.ogt.kq.service.bm.mq.SendMessageProducer;
import com.hyt.it.ogt.kq.service.bm.service.ISendMessageService;
import com.hyt.it.ogt.kq.service.gov.mapper.SendMessageMapper;
import com.hyt.it.ogt.kq.service.gov.model.param.MessageParam;
import com.hyt.it.ogt.kq.service.gov.model.param.MessageQueryParam;
import com.hyt.it.ogt.kq.service.gov.model.param.fjCityBm.SendMessageParam;
import com.hyt.it.ogt.kq.service.gov.model.vo.SendMessageVo;
import com.hyt.model.tps.vo.MessageVo;
import io.seata.common.util.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author 朱振宇
 * @since 2024-01-24
 */
@Service
@Slf4j
public class SendMessageServiceImpl implements ISendMessageService {

    public static final String LOCK_KEY = "sendMessage";

    public static final String SEND_MSG_TOTAL_PER_MINUTE_KEY = "sendMsgTotalPerMinute";

    @Resource
    private ConfigManager configManager;

    @Resource
    private SendMessageMapper sendMessageMapper;

    @Autowired
    private GovRedisLock redisLock;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private SendMessageProducer sendMessageProducer;

    @Resource
    private StudentProjectMapper studentProjectMapper;

    @Resource
    private ProjectMapper projectMapper;

    @Resource
    private TpsClient tpsClient;

    @Resource
    IProjectCollectInfoService iProjectCollectInfoService;

    /**
     * 通过MQ发送短信
     * @param officeId
     * @param deptId
     * @param userId
     * @param sendMessageParam
     */
    @Override
    public void sendMessageToMQ(String officeId, String deptId, String userId, SendMessageParam sendMessageParam) {
        if (!redisLock.tryLock(LOCK_KEY, sendMessageParam.getProjectId(), configManager.getSendMessageLockTime())) {
            throw new GovInvalidRequestException(ResponseCode.REPEAT_SEND_MESSAGE_EXCEPTION.getCode(), "当前发送短信进行中，请稍后重试");
        }
        checkSendMsgLimit();
        log.info("推送短信：uuId={}，message={}", sendMessageParam.getProjectId(), JSONObject.toJSONString(sendMessageParam));
        Project project = projectMapper.getProjectById(sendMessageParam.getProjectId());
        if (Objects.isNull(project)) {
            log.error("查询项目异常, projectId={}", sendMessageParam.getProjectId());
            throw new GovInvalidRequestException(ResponseCode.ERROR_PROJECT_ID_EXCEPTION.getCode(), "查询项目异常");
        }
        SendMessageTypeEnum messageType = SendMessageTypeEnum.getSendMessageTypeEnumByCode(sendMessageParam.getSendType());
        if (Objects.isNull(messageType)) {
            log.error("请求短信类型异常, sendType={}", sendMessageParam.getSendType());
            throw new GovInvalidRequestException(ResponseCode.NOT_SUITABLE_SEND_MESSAGE_EXCEPTION.getCode(), "当前没有符合条件的报名进行短信发送");
        }
        int studentTotal = getStudentTotal(project.getId(), project.getDeptId(), messageType);
        if (studentTotal == 0) {
            throw new GovInvalidRequestException(ResponseCode.NOT_SUITABLE_SEND_MESSAGE_EXCEPTION.getCode(), "当前没有符合条件的报名进行短信发送");
        }
        send(officeId, deptId, userId, sendMessageParam, project, messageType, studentTotal);
    }

    /**
     * 通过MQ发送短信
     * @param messageParam
     */
    public void sendMsg(MessageParam messageParam,String userId, String officeId) {
        if (!redisLock.tryLock(LOCK_KEY, messageParam.getProjectId(), configManager.getSendMessageLockTime())) {
            throw new GovInvalidRequestException(ResponseCode.REPEAT_SEND_MESSAGE_EXCEPTION.getCode(), "当前发送短信进行中，请稍后重试");
        }
        checkSendMsgLimit();
        log.info("推送短信：uuId={}，message={}", messageParam.getProjectId(), JSONObject.toJSONString(messageParam));
        Project project = projectMapper.getProjectById(messageParam.getProjectId());
        if (Objects.isNull(project)) {
            log.error("查询项目异常, projectId={}", messageParam.getProjectId());
            throw new GovInvalidRequestException(ResponseCode.ERROR_PROJECT_ID_EXCEPTION.getCode(), "查询项目异常");
        }
        // 如果没有机构采集信息则不设置机构权限查询
        Boolean hasOfficePermission = iProjectCollectInfoService.isComponentRequired(messageParam.getProjectId(), ComponentNameEnum.office.getCode());
        if (!hasOfficePermission) {
            messageParam.setDeptIds(null);
        }
        int  studentTotal = studentProjectMapper.getStudentCount(messageParam);
        if (studentTotal == 0) {
            throw new GovInvalidRequestException(ResponseCode.NOT_SUITABLE_SEND_MESSAGE_EXCEPTION.getCode(), "当前没有符合条件的报名进行短信发送");
        }
        executeSend(userId, messageParam, project, studentTotal,officeId);
    }



    /**
     * 获取报名人数
     * @param projectId
     * @param deptId
     * @param messageType
     * @return
     */
    public Integer getStudentTotal(String projectId, String deptId, SendMessageTypeEnum messageType) {
        int studentTotal;
        switch (messageType) {
            case ALL:
                studentTotal = studentProjectMapper.getAllStudentCount(projectId, deptId);
                break;
            case REGISTERED:
                studentTotal = studentProjectMapper.getRegisteredCount(projectId, deptId);
                break;
            case NOT_REGISTERED:
                studentTotal = studentProjectMapper.getNotRegisteredCount(projectId, deptId);
                break;
            default:
                throw new IllegalStateException("Unexpected value: " + messageType);
        }
        return studentTotal;
    }

    @Async("executor")
    public void executeSend(String userId, MessageParam messageParam,Project project,int studentTotal, String officeId){
        incrementSendMsgCount(studentTotal);
        String uuId = UUIDUtils.newSortUUID();
        messageParam.setSendLogId(uuId);
        int msgNum = calculateSmsCount(messageParam.getMessageContent());
        sendMessageMapper.saveSendMessageLog(SendMessageLog.builder()
                .id(uuId)
                .projectId(messageParam.getProjectId())
                .content(messageParam.getMessageContent())
                .createBy(userId)
                .createDate(LocalDateTime.now())
                .msgNum(msgNum)
                .msgTotal(msgNum * studentTotal)
                .build());
        sendStudentMessage(project, messageParam, officeId);
    }


    /**
     * 异步发送短信
     * @param officeId
     * @param deptId
     * @param userId
     * @param sendMessageParam
     * @param project
     * @param messageType
     * @param studentTotal
     */
    @Async("executor")
    public void send(String officeId, String deptId, String userId, SendMessageParam sendMessageParam, Project project, SendMessageTypeEnum messageType, int studentTotal) {
        incrementSendMsgCount(studentTotal);
        String uuId = UUIDUtils.newSortUUID();
        sendMessageParam.setSendLogId(uuId);
        int msgNum = calculateSmsCount(sendMessageParam.getMessageContent());
        sendMessageMapper.saveSendMessageLog(SendMessageLog.builder()
                .id(uuId)
                .projectId(sendMessageParam.getProjectId())
                .content(sendMessageParam.getMessageContent())
                .createBy(userId)
                .createDate(LocalDateTime.now())
                .msgNum(msgNum)
                .msgTotal(msgNum * studentTotal)
                .build());
        sendStudentList(project, sendMessageParam, messageType, studentTotal);
    }

    /**
     * 发送短信报名列表
     * @param project
     * @param sendMessageParam
     */
    public void sendStudentMessage(Project project, MessageParam sendMessageParam,String officeId) {

        List<RegisterMessageDTO> messageVoList = new ArrayList<>();
        List<BmStudentInfo> bmStudentInfoList = studentProjectMapper.queryStudentList(sendMessageParam);
        for (BmStudentInfo bmStudentInfo : bmStudentInfoList) {
            RegisterMessageDTO messageDTO = RegisterMessageDTO.builder()
                    .projectId(project.getId())
                    .sendLogId(sendMessageParam.getSendLogId())
                    .studentId(bmStudentInfo.getStudentId())
                    .receiver(bmStudentInfo.getPhone())
                    .messageVo(buildMessageVo(project.getProjectName(), project.getAddress(), bmStudentInfo.getName(), bmStudentInfo.getPhone(), sendMessageParam.getMessageContent(),officeId))
                    .build();
            messageVoList.add(messageDTO);
        }
        if (CollectionUtils.isNotEmpty(messageVoList)) {
            sendMessageProducer.sendMessage(FastJsonUtil.listToJson(messageVoList));
        }
        messageVoList.clear();
        bmStudentInfoList.clear();

    }

    /**
     * 发送短信报名列表
     * @param project
     * @param sendMessageParam
     * @param messageType
     * @param total
     */
    public void sendStudentList(Project project, SendMessageParam sendMessageParam, SendMessageTypeEnum messageType, int total) {
        int pageSize = 500;
        int pageTotal;
        if (total > 0) {
            if (total % pageSize == 0) {
                pageTotal = total / pageSize;
            } else {
                pageTotal = total / pageSize + 1;
            }
            List<RegisterMessageDTO> messageVoList = new ArrayList<>();
            List<BmStudentInfo> bmStudentInfoList = new ArrayList<>();
            for (int i = 1; i <= pageTotal; i++) {
                int startIndex = (i - 1) * pageSize;
                switch (messageType) {
                    case ALL:
                        bmStudentInfoList = studentProjectMapper.allStudentList(project.getId(), project.getDeptId(), startIndex, pageSize);
                        break;
                    case REGISTERED:
                        bmStudentInfoList = studentProjectMapper.registeredList(project.getId(), project.getDeptId(), startIndex, pageSize);
                        break;
                    case NOT_REGISTERED:
                        bmStudentInfoList = studentProjectMapper.notRegisteredList(project.getId(), project.getDeptId(), startIndex, pageSize);
                        break;
                }
                for (BmStudentInfo bmStudentInfo : bmStudentInfoList) {
                    RegisterMessageDTO messageDTO = RegisterMessageDTO.builder()
                            .projectId(project.getId())
                            .sendLogId(sendMessageParam.getSendLogId())
                            .studentId(bmStudentInfo.getStudentId())
                            .receiver(bmStudentInfo.getPhone())
                            .messageVo(buildMessageVo(project.getProjectName(), project.getAddress(), bmStudentInfo.getName(), bmStudentInfo.getPhone(), sendMessageParam.getMessageContent(),project.getOfficeId()))
                            .build();
                    messageVoList.add(messageDTO);
                }
                if (CollectionUtils.isNotEmpty(messageVoList)) {
                    sendMessageProducer.sendMessage(FastJsonUtil.listToJson(messageVoList));
                }
                messageVoList.clear();
                bmStudentInfoList.clear();
            }
        }
    }

    /**
     * 发送短信
     * @param messageDTOList
     */
    @Override
    public void sendMessage(List<RegisterMessageDTO> messageDTOList) {
        int workCount = 10;
        List<SendMessageLogCandidate> sendMessageLogCandidateList = Collections.synchronizedList(new ArrayList<>());
        if (messageDTOList.size() <= workCount) {
            processMessage(messageDTOList, sendMessageLogCandidateList);
        } else {
            int batchSize = messageDTOList.size() / workCount;
            List<CompletableFuture<Void>> futures = new ArrayList<>();
            for (int i = 0; i < workCount; i++) {
                int start = i * batchSize;
                int end = (i == workCount - 1) ? messageDTOList.size() : (i + 1) * batchSize;
                List<RegisterMessageDTO> subList = messageDTOList.subList(start, end);
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> processMessage(subList, sendMessageLogCandidateList));
                futures.add(future);
            }
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        }
        sendMessageMapper.saveSendMessageLogCandidate(sendMessageLogCandidateList);
    }

    @Override
    public List<SendMessageTemplate> findSendMessageTemplateList(String officeId) {
        return sendMessageMapper.findSendMessageTemplateList(officeId);
    }

    /**
     * 获取发送短信数
     * @param projectId
     * @param deptId
     * @param sendType
     * @param messageNum
     * @return
     */
    @Override
    public Integer getSendMessageNum(String projectId, String deptId, Integer sendType, Integer messageNum) {
        SendMessageTypeEnum messageType = SendMessageTypeEnum.getSendMessageTypeEnumByCode(sendType);
        if (Objects.isNull(messageType)) {
            log.error("请求短信类型异常, sendType={}", sendType);
            throw new GovInvalidRequestException(ResponseCode.NOT_SUITABLE_SEND_MESSAGE_EXCEPTION.getCode(), "当前没有符合条件的报名进行短信发送");
        }
        return (int) Math.ceil((double) messageNum / 60) * getStudentTotal(projectId, deptId, messageType);
    }


    /**
     * 获取发送短信数
     * @param param
     * @return
     */
    @Override
    public Integer querySendMessageNum(MessageParam param) {
        Boolean hasOfficePermission = iProjectCollectInfoService.isComponentRequired(param.getProjectId(), ComponentNameEnum.office.getCode());
        if (!hasOfficePermission) {
            param.setDeptIds(null);
        }
        int messageNum = param.getMessageContent().length();
        int  studentTotal = studentProjectMapper.getStudentCount(param);
        return (int) Math.ceil((double) messageNum / 65) * studentTotal;
    }

    /**
     * 处理发送短信逻辑
     * @param messageDTOList
     * @param sendMessageLogCandidateList
     */
    private void processMessage(List<RegisterMessageDTO> messageDTOList, List<SendMessageLogCandidate> sendMessageLogCandidateList) {
        if (messageDTOList.get(0).getRepeatContent()) {
            List<String> mobileNos = new ArrayList<>();
            for (RegisterMessageDTO messageDTO : messageDTOList) {
                SendMessageLogCandidate logCandidate = SendMessageLogCandidate.builder()
                        .sendLogId(messageDTO.getSendLogId())
                        .projectId(messageDTO.getProjectId())
                        .studentId(messageDTO.getStudentId())
                        .receiver(messageDTO.getReceiver())
                        .content(messageDTO.getMessageVo().getContent())
                        .build();
                logCandidate.setId(UUIDUtils.randomUUID());
                logCandidate.setCreateDate(LocalDateTime.now());
                logCandidate.setUpdateDate(LocalDateTime.now());
                sendMessageLogCandidateList.add(logCandidate);
                mobileNos.add(messageDTO.getReceiver());
            }
            MessageVo messageVo = messageDTOList.get(0).getMessageVo();
            messageVo.setMobileNum(mobileNos.toArray(new String[0]));
            String result = tpsClient.sendMessage(messageVo);
            JSONObject jsonObject = JSONObject.parseObject(result);
            boolean success = jsonObject.getBoolean("success");
            if (success) {
                sendMessageLogCandidateList.forEach(messageLogCandidate -> messageLogCandidate.setSendState(MessageSendStatusCode.SEND_SUCCESS.getCode()));
            } else {
                sendMessageLogCandidateList.forEach(messageLogCandidate -> messageLogCandidate.setSendState(MessageSendStatusCode.SEND_FAIL.getCode()));
            }
        } else {
            for (RegisterMessageDTO messageDTO : messageDTOList) {
                SendMessageLogCandidate logCandidate = SendMessageLogCandidate.builder()
                        .sendLogId(messageDTO.getSendLogId())
                        .projectId(messageDTO.getProjectId())
                        .studentId(messageDTO.getStudentId())
                        .receiver(messageDTO.getReceiver())
                        .content(messageDTO.getMessageVo().getContent())
                        .build();
                logCandidate.setId(UUIDUtils.randomUUID());
                logCandidate.setCreateDate(LocalDateTime.now());
                logCandidate.setUpdateDate(LocalDateTime.now());
                sendMessageLogCandidateList.add(logCandidate);
                String result = tpsClient.sendMessage(messageDTO.getMessageVo());
                logCandidate.setSendResult(result);
                JSONObject jsonObject = JSONObject.parseObject(result);
                boolean success = jsonObject.getBoolean("success");
                if (success) {
                    logCandidate.setSendState(MessageSendStatusCode.SEND_SUCCESS.getCode());
                } else {
                    logCandidate.setSendState(MessageSendStatusCode.SEND_FAIL.getCode());
                }
            }
        }
        decrementSendMsgCount(messageDTOList.size());
    }


    public MessageVo buildMessageVo(String projectName, String address, String studentName, String phone, String content,String officeId) {
        MessageVo messageVo = new MessageVo();
        messageVo.setOfficeId(officeId);
        messageVo.setMobileNum(new String[]{phone});
        messageVo.setContent(content);
//        messageVo.setContent(SendContentReplaceUtils.replace(SendContentTemplateEnum.REGISTER.getCode(), content,
//                new String[]{studentName, projectName, address}));
        return messageVo;
    }

    /**
     * 检查短信是否超限
     */
    private void checkSendMsgLimit() {
        Object num = redisTemplate.opsForValue().get(SEND_MSG_TOTAL_PER_MINUTE_KEY);
        if (configManager.getSendMessageTotalPerMinute() == -1 || (Objects.nonNull(num) && Integer.parseInt(num.toString()) > configManager.getSendMessageTotalPerMinute())) {
            throw new GovInvalidRequestException(ResponseCode.TOO_MANY_SEND_MESSAGE_EXCEPTION.getCode(), "当前发送中的短信已超限，请稍后重试");
        }
    }

    /**
     * 计算短信内容字数
     * @param content
     * @return
     */
    private int calculateSmsCount(String content) {
        return (int) Math.ceil((double) content.length() / 60);
    }

    private void incrementSendMsgCount(int count) {
        redisTemplate.opsForValue().increment(SEND_MSG_TOTAL_PER_MINUTE_KEY, count);
        redisTemplate.expire(SEND_MSG_TOTAL_PER_MINUTE_KEY, 1, TimeUnit.MINUTES);
    }

    private void decrementSendMsgCount(int count) {
        redisTemplate.opsForValue().decrement(SEND_MSG_TOTAL_PER_MINUTE_KEY, count);
    }

    public IPage<SendMessageVo> queryMessage(MessageQueryParam param){
        IPage<SendMessageVo> page = sendMessageMapper.queryMessage(param);
        return page;
    }

}
